package com.example.wujinapi.comm.wechat;

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.core.io.ClassPathResource;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.X509Certificate;

/**
 * 微信签名相关工具类
 *
 * @auther fub
 * @date 2021-06-21 22:35
 */
public class WxSignUtils {

    public static final String AES = "AES";
    public static final String AES_CBC_PADDING = "AES/CBC/PKCS7Padding";
    private static final String CHARSET_ENCODING = "UTF-8";
    private static final String ALGORITHM = "SHA256withRSA";


    /****************************************获取公共请求HttpClient****************************************/
    /**
     * 获取CloseableHttpClient
     *
     * @param merchantId 商户id
     * @param apiV3ey    v3key
     * @return {@link CloseableHttpClient}
     * @throws IOException
     */
    public static CloseableHttpClient getHttpClientBuilder(String merchantId, String apiV3ey, String certPath, String keyPath) throws IOException {
        // 1. 获取SerialNumber
        String serialNumber = getApiSerialNumber(certPath);
        // 2. 获取私钥
        PrivateKey privateKey = getApiPrivateKey(keyPath);
        // 3. 获取支付平台证书信息
        AutoUpdateCertificatesVerifier wxVerifier = getWxVerifier(merchantId, serialNumber, privateKey, apiV3ey);
        // 4. 构造WechatPayHttpClientBuilder
        WechatPayHttpClientBuilder httpClientBuilder = WechatPayHttpClientBuilder.create()
                .withMerchant(merchantId, serialNumber, privateKey)
                .withValidator(new WechatPay2Validator(wxVerifier));
        // 5. 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
        return httpClientBuilder.build();
    }

    /**
     * 自动更新支付证书
     * 不需要传入微信支付证书了
     *
     * @param merchantId   商户id
     * @param serialNumber api证书的序列号
     * @param privateKey   api证书的私钥
     * @param apiV3Key     v3key
     * @return {@link AutoUpdateCertificatesVerifier}
     * @throws UnsupportedEncodingException
     */
    public static AutoUpdateCertificatesVerifier getWxVerifier(String merchantId, String serialNumber, PrivateKey privateKey, String apiV3Key) throws UnsupportedEncodingException {
        return new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(merchantId, new PrivateKeySigner(serialNumber, privateKey)),
                apiV3Key.getBytes("utf-8"));
    }

    /********************************************获取证书密钥相关信息*************************************************/
    /**
     * 获取微信支付证书序列号
     *
     * @return 证书序列号
     * @throws IOException IO异常
     */
    public static String getWxSerialNumber(AutoUpdateCertificatesVerifier wxVerifier) {
        X509Certificate validCertificate = wxVerifier.getValidCertificate();
        return validCertificate.getSerialNumber().toString(16).toUpperCase();
    }

    /**
     * 服务商私钥签名
     *
     * @param srcData 需要签名的数据
     * @return 签名后数据
     */
    public static String signServer(String srcData) {
        if (srcData == null) {
            return "";
        }
        try {
            PrivateKey privateKey = WxSignUtils.getApiPrivateKey(WxConstants.API_CLIENT_SERVER_KEY_PATH);
            // 进行签名服务
            Signature signature = Signature.getInstance(ALGORITHM);
            signature.initSign(privateKey);
            signature.update(srcData.getBytes(CHARSET_ENCODING));
            byte[] signedData = signature.sign();
            return java.util.Base64.getEncoder().encodeToString(signedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 商户私钥签名
     *
     * @param srcData 需要签名的数据
     * @return 签名后数据
     */
    public static String signMerchant(String srcData) {
        if (srcData == null) {
            return "";
        }
        try {
            PrivateKey privateKey = WxSignUtils.getApiPrivateKey(WxConstants.API_CLIENT_MERCHANT_KEY_PATH);
            // 进行签名服务
            Signature signature = Signature.getInstance(ALGORITHM);
            signature.initSign(privateKey);
            signature.update(srcData.getBytes(CHARSET_ENCODING));
            byte[] signedData = signature.sign();
            return java.util.Base64.getEncoder().encodeToString(signedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 商户公钥验签
     *
     * @param srcData    原数据
     * @param signedData 签名后的数据
     * @param publicKey  商户公钥
     * @return 验签是否成功
     */
    public static boolean verify(String srcData, String signedData, PublicKey publicKey) {
        if (srcData == null || signedData == null || publicKey == null) {
            return false;
        }
        try {
            Signature sign = Signature.getInstance(ALGORITHM);
            sign.initVerify(publicKey);
            sign.update(srcData.getBytes(CHARSET_ENCODING));
            return sign.verify(java.util.Base64.getDecoder().decode(signedData));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 平台公钥加密
     * 从Verifier中获得微信支付平台证书
     *
     * @param verifier 微信支付平台证书
     * @param text     待加密信息
     * @return 密文
     */
    public static String encryptOAEP(AutoUpdateCertificatesVerifier verifier, String text) throws IllegalBlockSizeException {
        // 从Verifier中获得微信支付平台证书
        X509Certificate certificate = verifier.getValidCertificate();
        return RsaCryptoUtil.encryptOAEP(text, certificate);
    }

    /**
     * 商户私钥解密
     *
     * @param privateKey 商户私钥
     * @param text       待加密信息
     * @return 密文
     */
    public static String decryptOAEP(PrivateKey privateKey, String text) throws BadPaddingException {
        return RsaCryptoUtil.decryptOAEP(text, privateKey);
    }

    /**
     * 获取API证书序列号
     *
     * @param certPath /wx/server_apiclient_cert.pem
     * @return 证书序列号
     * @throws IOException IO异常
     */
    public static String getApiSerialNumber(String certPath) {
        try {
            ClassPathResource resource = new ClassPathResource(certPath);
            X509Certificate x509Certificate = PemUtil.loadCertificate(resource.getInputStream());
            return x509Certificate.getSerialNumber().toString(16).toUpperCase();
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 获取API证书公钥
     *
     * @param certPath 证书路径 /wx/server_apiclient_cert.pem
     * @return API证书公钥
     * @throws IOException IO异常
     */
    public static PublicKey getApiPublicKey(String certPath) {
        try {
            ClassPathResource resource = new ClassPathResource(certPath);
            X509Certificate x509Certificate = PemUtil.loadCertificate(resource.getInputStream());
            return x509Certificate.getPublicKey();
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 获取API证书私钥
     *
     * @param keyPath 证书路径 /wx/server_apiclient_key.pem
     * @return API证书私钥
     * @throws IOException IO异常
     */
    public static PrivateKey getApiPrivateKey(String keyPath) {
        try {
            ClassPathResource resource = new ClassPathResource(keyPath);
            return PemUtil.loadPrivateKey(resource.getInputStream());
        } catch (IOException e) {
            return null;
        }
    }

    /*********************************************小程序登陆获取用户信息解密***********************************************/
    /**
     * 开放数据校验与解密
     * 对称解密使用的算法为 AES-128-CBC，数据采用PKCS#7填充
     * 对称解密的目标密文:encrypted=Base64_Decode(encryptData)
     * 对称解密秘钥:key = Base64_Decode(session_key),aeskey是16字节
     * 对称解密算法初始向量:iv = Base64_Decode(iv),同样是16字节
     *
     * @param encrypted   目标密文
     * @param session_key 会话ID
     * @param iv          加密算法的初始向量
     * @return 明文
     */
    public static String wxDecrypt(String encrypted, String session_key, String iv) {
        String result = null;
        byte[] encrypted64 = Base64.decodeBase64(encrypted);
        byte[] key64 = Base64.decodeBase64(session_key);
        byte[] iv64 = Base64.decodeBase64(iv);
        try {
            init();
            result = new String(decrypt(encrypted64, key64, generateIV(iv64)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * description: 初始化密钥
     *
     * @throws Exception
     */
    public static void init() throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        KeyGenerator.getInstance(AES).init(128);
    }

    /**
     * description: 生成iv
     *
     * @param iv 向量
     * @return {@link AlgorithmParameters}
     * @throws Exception
     */
    public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
        // iv 为一个 16 字节的数组，这里采用和 iOS 端一样的构造方法，数据全为0
        // Arrays.fill(iv, (byte) 0x00);
        AlgorithmParameters params = AlgorithmParameters.getInstance(AES);
        params.init(new IvParameterSpec(iv));
        return params;
    }

    /**
     * description: 执行解密操作
     *
     * @param encryptedData 加密后的字符串
     * @param keyBytes      密钥key
     * @param iv            便宜向量iv
     * @return byte[]
     * @throws Exception
     */
    public static byte[] decrypt(byte[] encryptedData, byte[] keyBytes, AlgorithmParameters iv) throws Exception {
        Key key = new SecretKeySpec(keyBytes, AES);
        Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
        // 设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        return cipher.doFinal(encryptedData);
    }


}
